﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Ait = Authorit.API;
using AuthoritExtensibility;
using CopyFolder.Properties;
using Authorit.API.Utilities;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

[assembly: CLSCompliant(true)]
namespace CopyFolder
{
    public class PluginManager : IPlugin
    {
        #region Fields (15) 

        private AitApplicationProxy proxy;
        private Progress_Dialog progDialog;
        private BackgroundWorker bwGlobal = new BackgroundWorker();
        #endregion Fields 

        #region Constructors (1) 

        public PluginManager(AitApplicationProxy Proxy)
        {
            bwGlobal.WorkerReportsProgress = Settings.Default.ShowProgressDialog;
            bwGlobal.WorkerSupportsCancellation = Settings.Default.AllowCancel;
            bwGlobal.DoWork += new DoWorkEventHandler(bwGlobal_DoWork);
            bwGlobal.ProgressChanged += new ProgressChangedEventHandler(bwGlobal_ProgressChanged);
            bwGlobal.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwGlobal_RunWorkerCompleted);
            proxy = Proxy;
            if (Settings.Default.ShowOnToolbar)
            {
                ToolbarAddArgs toolbarArgs = new ToolbarAddArgs();
                if (Settings.Default.ShowLargeIcon)
                {
                    toolbarArgs.IconImage = Resources.CopyFolder;
                }
                else
                {
                    toolbarArgs.IconImage = Resources.CopyFolderSmall;
                }
                toolbarArgs.PluginName = Resources.PluginName;
                if (Settings.Default.ShowNameOnToolbar)
                {
                    toolbarArgs.TargetButtonName = Resources.ToolbarButtonName;
                }
                else
                {
                    toolbarArgs.TargetButtonName = String.Empty;
                }
                toolbarArgs.Tooltip = Resources.ToolbarButtonTooltip;
                toolbarArgs.OnClick = clickHandler;
                toolbarArgs.TargetGroupName = Settings.Default.ToolbarButtonTargetGroupName;
                Proxy.AddPluginToToolbar(toolbarArgs);
            }
        }

        void bwGlobal_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //TODO: Implement ability to cancel operation
            //TODO: Implement check for why worker completed (exception, cancelled, success)
            LibraryRefreshArgs libRefresh = new LibraryRefreshArgs();
            libRefresh.Type = LibraryRefreshType.FolderRefresh;
            proxy.RefreshLibrary(libRefresh);
            if (Settings.Default.ShowProgressDialog)
            {
                progDialog.Close();
            }
            progDialog.Dispose();
        }

        void bwGlobal_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progDialog.UpdateProgress((string)e.UserState);
        }

        void bwGlobal_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            List<object> args = (List<object>)e.Argument;
            System.Collections.Generic.Dictionary<int, int> copiedObjects = CopyToFolder((int)args[0], (int)args[1], (bool)args[2], (AuthoritEventArgs)args[3], worker, e);
            e.Result = ReplaceObjects(copiedObjects, (AuthoritEventArgs)args[3], worker, e);
        }

        #endregion Constructors 

        #region Methods (9) 

        // Private Methods (9) 

        private void clickHandler(object sender, AuthoritEventArgs args)
        {
            FolderSelector selectFolderForm = new FolderSelector();
            selectFolderForm.AitEventArgs = args;
            DialogResult dr = selectFolderForm.ShowDialog();
            switch (dr)
            {
                case DialogResult.Abort:
                    break;
                case DialogResult.Cancel:
                    break;
                case DialogResult.Ignore:
                    break;
                case DialogResult.No:
                    break;
                case DialogResult.None:
                    break;
                case DialogResult.OK:
                    bool copyReleaseStates = selectFolderForm.CopyReleaseStates;
                    List<object> bwArgs = new List<object>();
                    bwArgs.Add(args.CurrentFolderId);
                    bwArgs.Add(selectFolderForm.TargetFolderId);
                    bwArgs.Add(selectFolderForm.CopyReleaseStates);
                    bwArgs.Add(args);
                    bwGlobal.RunWorkerAsync(bwArgs);
                    if (Settings.Default.ShowProgressDialog)
                    {
                        progDialog = new Progress_Dialog();
                        progDialog.Show();
                    }
                    break;
                case DialogResult.Retry:
                    break;
                case DialogResult.Yes:
                    break;
                default:
                    break;
            }
            selectFolderForm.Dispose();

        }

        private static string GetObjectTypeString(Ait.Dto.ObjectType objectType)
        {
            string objectTypeString = string.Empty;
            switch (objectType)
            {
                case Authorit.API.Dto.ObjectType.Book:
                    objectTypeString = Resources.UiNameBook;
                    break;
                case Authorit.API.Dto.ObjectType.File:
                    objectTypeString = Resources.UiNameFile;
                    break;
                case Authorit.API.Dto.ObjectType.Glossary:
                    objectTypeString = Resources.UiNameGlossary;
                    break;
                case Authorit.API.Dto.ObjectType.Hyperlink:
                    objectTypeString = Resources.UiNameHyperlink;
                    break;
                case Authorit.API.Dto.ObjectType.Index:
                    objectTypeString = Resources.UiNameIndex;
                    break;
                case Authorit.API.Dto.ObjectType.IndexEntry:
                    objectTypeString = Resources.UiNameIndexEntry;
                    break;
                case Authorit.API.Dto.ObjectType.Macro:
                    objectTypeString = Resources.UiNameMacro;
                    break;
                case Authorit.API.Dto.ObjectType.Media:
                    objectTypeString = Resources.UiNameMedia;
                    break;
                case Authorit.API.Dto.ObjectType.PublishProfile:
                    objectTypeString = Resources.UiNamePublishingProfile;
                    break;
                case Authorit.API.Dto.ObjectType.Style:
                    objectTypeString = Resources.UiNameStyle;
                    break;
                case Authorit.API.Dto.ObjectType.TableOfContents:
                    objectTypeString = Resources.UiNameTableOfContents;
                    break;
                case Authorit.API.Dto.ObjectType.TableOfFigures:
                    objectTypeString = Resources.UiNameTableOfFigures;
                    break;
                case Authorit.API.Dto.ObjectType.TitlePage:
                    objectTypeString = Resources.UiNameTitlePage;
                    break;
                case Authorit.API.Dto.ObjectType.Topic:
                    objectTypeString = Resources.UiNameTopic;
                    break;
                default:
                    break;
            }
            return objectTypeString;
        }

        private System.Collections.Generic.Dictionary<int, int> CopyToFolder(int sourceId, int targetId, bool copyReleaseStates, AuthoritEventArgs aitArgs, BackgroundWorker worker, DoWorkEventArgs e)
        {
            HashSet<int> newFolders = new HashSet<int>();
            Ait.ApplicationContext appContext = PluginUtility.GetAppContext(aitArgs);
            Ait.Services.Authentication authService = appContext.GetServiceInstance<Ait.Services.Authentication>();
            if (!authService.IsLoggedIn())
            {
                authService.Login(appContext.LibrariesConfiguration[0].Alias, aitArgs.Username, aitArgs.Password);
            }
            Ait.Services.ObjectCommon objectCommonService = appContext.GetServiceInstance<Ait.Services.ObjectCommon>();
            Ait.Services.Folder folderService = appContext.GetServiceInstance<Ait.Services.Folder>();
            int suffix = Settings.Default.FolderNameSuffixStartNumber;
            string sourceFolderName;
            string newFolderName;
            Ait.Dto.Folder sourceFolder;
            if (sourceId > 0)
            {
                sourceFolder = folderService.Get(sourceId);
                sourceFolderName = sourceFolder.Name;
            }
            else
            {
                sourceFolderName = appContext.LibrariesConfiguration[0].Alias;
            }
            newFolderName = sourceFolderName;
            while (folderService.CheckSubfolderExists(targetId, newFolderName))
            {
                string suffixString = suffix.ToString(System.Globalization.CultureInfo.CurrentCulture);
                string padString = String.Empty;
                if (suffixString.Length < Settings.Default.FolderNameSuffixDigits)
                {
                    int pads = Settings.Default.FolderNameSuffixDigits - suffixString.Length;
                    padString = new string(Settings.Default.FolderNameSuffixPaddingCharacter, pads);
                }
                newFolderName = sourceFolderName + padString + suffixString;
                suffix += Settings.Default.FolderNameSuffixIncrementBy;
            }
            Ait.Dto.Folder targetFolder = folderService.Create(targetId, newFolderName);
            newFolders.Add(targetFolder.FolderId);
            System.Collections.Generic.Dictionary<int, int> copiedObjects = new Dictionary<int, int>();
            Stack<KeyValuePair<int, int>> folderPairs = new Stack<KeyValuePair<int, int>>();
            folderPairs.Push(new KeyValuePair<int, int>(sourceId, targetFolder.FolderId));
            while (folderPairs.Count > 0)
            {
                KeyValuePair<int, int> folderPair = folderPairs.Pop();
                int sourceFolderId = folderPair.Key;
                int targetFolderId = folderPair.Value;
                List<Ait.Dto.ObjectBrief> folderObjects = (List<Ait.Dto.ObjectBrief>)objectCommonService.GetObjectsInFolder(sourceFolderId, Ait.Dto.ObjectListSort.ObjectType, Ait.Dto.SortDirection.Ascending);
                foreach (Ait.Dto.ObjectBrief sourceObject in folderObjects)
                {
                    if (worker.WorkerReportsProgress)
                    {
                        string action = "Copying {0} ({1})";
                        action = String.Format(System.Globalization.CultureInfo.CurrentCulture, action, sourceObject.Description, sourceObject.ObjectId);
                        worker.ReportProgress(0, action);
                    }
                    Ait.Dto.ObjectBrief targetObject = objectCommonService.Duplicate(sourceObject.ObjectId);
                    targetObject = objectCommonService.Move(targetObject.ObjectId, targetFolderId);
                    targetObject.Description = targetObject.Description.Replace(Settings.Default.DuplicateObjectDescriptionReplacementString, String.Empty);
                    if (copyReleaseStates)
                    {
                        targetObject.ReleaseStateId = sourceObject.ReleaseStateId;
                    }
                    targetObject = objectCommonService.UpdateObjectBrief(targetObject);
                    copiedObjects.Add(sourceObject.ObjectId, targetObject.ObjectId);
                }
                foreach (Ait.Dto.Folder childFolder in folderService.GetChildren(sourceFolderId))
                {
                    if (!newFolders.Contains(childFolder.FolderId))
                    {
                        Ait.Dto.Folder targetChildFolder = folderService.Create(targetFolderId, childFolder.Name);
                        newFolders.Add(targetChildFolder.FolderId);
                        folderPairs.Push(new KeyValuePair<int, int>(childFolder.FolderId, targetChildFolder.FolderId));
                    }
                }
            }
            return copiedObjects;
        }

        private int ReplaceObjects(System.Collections.Generic.Dictionary<int, int> copiedObjects, AuthoritEventArgs aitArgs, BackgroundWorker sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            Ait.ApplicationContext appContext = PluginUtility.GetAppContext(aitArgs);
            Ait.Services.Book bookService = appContext.GetServiceInstance<Ait.Services.Book>();
            Ait.Services.File fileService = appContext.GetServiceInstance<Ait.Services.File>();
            Ait.Services.Hyperlink hyperlinkService = appContext.GetServiceInstance<Ait.Services.Hyperlink>();
            Ait.Services.ObjectCommon objectCommonService = appContext.GetServiceInstance<Ait.Services.ObjectCommon>();
            Ait.Services.Topic topicService = appContext.GetServiceInstance<Ait.Services.Topic>();
            Ait.Services.Authentication authService = appContext.GetServiceInstance<Ait.Services.Authentication>();
            if (!authService.IsLoggedIn())
            {
                authService.Login(appContext.LibrariesConfiguration[0].Alias, aitArgs.Username, aitArgs.Password);
            }
            
            if (copiedObjects.Count > 0)
            {
                objectCommonService.LockMultiple(copiedObjects.Values.ToList());
            }
            foreach (KeyValuePair<int, int> pair in copiedObjects)
            {
                int sourceObjectId = pair.Key;
                int targetObjectId = pair.Value;
                Ait.Dto.ObjectBrief targetObject = objectCommonService.GetObjectBrief(targetObjectId);
                XElement targetXml = XElement.Parse(objectCommonService.GetXml(targetObjectId));
                XNamespace aitNs = XNamespace.Get(@"http://www.authorit.com/xml/authorit");
                if (sender.WorkerReportsProgress)
                {
                    string progressMessage = "Checking {0} ({1}) for replacement";
                    progressMessage = String.Format(System.Globalization.CultureInfo.CurrentCulture, progressMessage, targetObject.Description, targetObject.ObjectId);
                    worker.ReportProgress(0, progressMessage);
                }
                switch (targetObject.ObjectType)
                {
                    case Authorit.API.Dto.ObjectType.Book:
                        Ait.Dto.Book targetBook = bookService.Get(targetObjectId);
                        targetBook.TableOfContents = ReplaceToc((List<Ait.Dto.BookTocEntry>)targetBook.TableOfContents, copiedObjects);
                        targetBook.BasedOnId = ReplaceWith(targetBook.BasedOnId, copiedObjects);
                        bookService.Update(targetBook);
                        objectCommonService.Unlock(targetBook.ObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.File:
                        if (!targetObject.IsTemplate)
                        {
                            Ait.Dto.File targetFile = fileService.Get(targetObjectId);
                            targetFile.BasedOnId = ReplaceWith(targetFile.BasedOnId, copiedObjects);
                            targetFile.HyperlinkId = ReplaceWith(targetFile.HyperlinkId, copiedObjects);
                            targetFile.VariantParentId = ReplaceWith(targetFile.VariantParentId, copiedObjects);
                            fileService.Update(targetFile); 
                        }
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Glossary:
                        //targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        string basedOnId = targetXml.Descendants(aitNs + "BasedOn").First().Value;
                        targetXml.Descendants(aitNs + "BasedOn").First().SetValue(ReplaceWith(basedOnId, copiedObjects));
                        string printMediaId = targetXml.Descendants(aitNs + "PrintMedia").First().Value;
                        targetXml.Descendants(aitNs + "PrintMedia").First().SetValue(ReplaceWith(printMediaId, copiedObjects));
                        objectCommonService.SaveXml(targetXml.ToString(), false, false);
                        //TODO: Implement fix for work item 2673 http://aitcopyfolder.codeplex.com/workitem/2673
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Hyperlink:
                        Ait.Dto.Hyperlink targetLink = hyperlinkService.Get(targetObjectId);
                        targetLink.BasedOnId = ReplaceWith(targetLink.BasedOnId, copiedObjects);
                        for (int i = 0; i < targetLink.TopicTargets.Count; i++)
                        {
                            targetLink.TopicTargets[i] = ReplaceWith(targetLink.TopicTargets[i], copiedObjects);
                        }
                        targetLink.VariantParentId = ReplaceWith(targetLink.VariantParentId, copiedObjects);
                        hyperlinkService.Update(targetLink);
                        objectCommonService.Unlock(targetLink.ObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Index:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        //TODO: Implement fix for work item 2668 http://aitcopyfolder.codeplex.com/workitem/2668
                        //TODO: Implement fix for work item 2669 http://aitcopyfolder.codeplex.com/workitem/2669
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.IndexEntry:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        //TODO: Implement fix for work item 2670 http://aitcopyfolder.codeplex.com/workitem/2670
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Macro:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Media:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.PublishProfile:
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Style:
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.TableOfContents:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        //TODO: Implement fix for work item 26667 http://aitcopyfolder.codeplex.com/workitem/2667
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.TableOfFigures:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        //TODO: Implement fix for work item 2673 http://aitcopyfolder.codeplex.com/workitem/2673
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.TitlePage:
                        targetObject.BasedOnId = ReplaceWith(targetObject.BasedOnId, copiedObjects);
                        //TODO: Implement fix for work item 2666 http://aitcopyfolder.codeplex.com/workitem/2666
                        objectCommonService.Unlock(targetObjectId);
                        break;
                    case Authorit.API.Dto.ObjectType.Topic:
                        Ait.Dto.Topic targetTopic = topicService.Get(targetObjectId);
                        targetTopic.BasedOnId = ReplaceWith(targetTopic.BasedOnId, copiedObjects);
                        targetTopic.VariantParentId = ReplaceWith(targetTopic.VariantParentId, copiedObjects);
                        //TODO: Implement fix for work item 2671 http://aitcopyfolder.codeplex.com/workitem/2671
                        topicService.Update(targetTopic);
                        string topicText = ReplaceTopicTextXml(topicService.GetTopicTextXml(targetTopic.ObjectId), copiedObjects);
                        topicService.SaveTopicTextFromXml(targetTopic.ObjectId, topicText);
                        objectCommonService.Unlock(targetTopic.ObjectId);
                        break;
                    default:
                        break;
                }
            }
            return copiedObjects.Count;
        }

        private static void ReplaceRefIds(ref XmlNodeList nodes, Dictionary<int,int> copiedObjects)
        {
            for (int i = 0; i < nodes.Count; i++)
            {
                int refId = Convert.ToInt32(nodes[i].Attributes["id"].Value, System.Globalization.CultureInfo.InvariantCulture);
                if (copiedObjects.ContainsKey(refId))
                {
                    nodes[i].Attributes["id"].Value = copiedObjects[refId].ToString(System.Globalization.CultureInfo.InvariantCulture);
                }
            }
        }

        private static List<Ait.Dto.BookTocEntry> ReplaceToc(List<Ait.Dto.BookTocEntry> toc, Dictionary<int,int> copiedObjects)
        {
            for (int i = 0; i < toc.Count; i++)
            {
                List<Ait.Dto.BookTocEntry> tocChildren = new List<Ait.Dto.BookTocEntry>();
                if (copiedObjects.ContainsKey(toc[i].ObjectId))
                {
                    toc[i].ObjectId = copiedObjects[toc[i].ObjectId];
                }
                toc[i].Children = ReplaceToc((List<Ait.Dto.BookTocEntry>)toc[i].Children, copiedObjects);
            }
            return toc;
        }

        private static string ReplaceTopicTextXml(string topicText, Dictionary<int,int> copiedObjects)
        {
            XmlDocument textXml = new XmlDocument();
            textXml.LoadXml(topicText);
            XmlNodeList fileRefNodes = textXml.SelectNodes(@"//fref");
            ReplaceRefIds(ref fileRefNodes, copiedObjects);
            XmlNodeList topicRefNodes = textXml.SelectNodes(@"//tref");
            ReplaceRefIds(ref topicRefNodes, copiedObjects);
            XmlNodeList hlinkRefNodes = textXml.SelectNodes(@"//href");
            ReplaceRefIds(ref hlinkRefNodes, copiedObjects);
            XmlNodeList macroRefNodes = textXml.SelectNodes(@"//mref");
            ReplaceRefIds(ref macroRefNodes, copiedObjects);
            return textXml.InnerXml;
        }

        private static int ReplaceWith(int lookupId, Dictionary<int,int> copiedObjects)
        {
            int replaceId = lookupId;
            if (copiedObjects.ContainsKey(lookupId))
            {
                replaceId = copiedObjects[lookupId];
            }
            return replaceId;
        }

        private static string ReplaceWith(string lookupId, Dictionary<int,int> copiedObjects)
        {
            int id;
            if (Int32.TryParse(lookupId, out id))
            {
                return ReplaceWith(id, copiedObjects).ToString(System.Globalization.CultureInfo.InvariantCulture);
            }
            else
            {
                return lookupId;
            }
        }

        #endregion Methods 
    }
}
